#ifndef T_AUTOMA_MODULES_SLAVES_GENERALPROTOTYPE_HPP
#define T_AUTOMA_MODULES_SLAVES_GENERALPROTOTYPE_HPP

#include<string>
#include<vector>
#include<time.h>
#include <set>
#include <map>

#include "../../Comms/commsInterface.hpp"
#include "slaveErrorCodes.hpp"

#ifndef U8Vec
#define U8Vec std::vector<uint8_t>
#endif

/*
General slave behaiour

Connect:

Execute:
  The default state is stand-by
	Not updated nor executing anything
	Can be activated
  A slave must be activated to be updated in protocol loop
	But an activated slave may not execute its schedule right away
	Most robotic slaves can execute immediate command in activated
  Start planned execution by startSchedule

*/


typedef void(__stdcall* MasterLogCallback)(std::wstring name, std::wstring id,
	std::string type, U8Vec log);
typedef void(__stdcall* MasterLogStateCallback)(std::wstring name, std::wstring id,
	std::string type, U8Vec log, U8Vec hint);

struct ParaMapSlave {
	std::map<std::wstring, std::wstring> paraMap;

	int has(std::wstring key);
	int getInt(std::wstring key);
	int getFloat(std::wstring key);

	float extractConfigSpeed(std::wstring configStr);
};

// general slave representative
struct GeneralSlave {
	enum struct ConnectionStatus {
		// connected but the broken
		Broken = -1,
		Missing = 0,
		Detected = 1,
		Connected = 2
	};
	enum struct ExecutionStatus {
		NotAvailable = -1,
		StandBy = 0,
		Activated = 1
	};

	GeneralSlave();
	~GeneralSlave();

	void genSlaveInit();
	void genSlaveInit(GeneralSlave* ref);
	void genSlaveCopy(GeneralSlave* ref);
	void genSlaveRelease();
	void setNewLogFile(std::wstring filename, bool flagErase);
	int setNewFriendlyName(std::wstring name);
	void setNewReferenceName(std::wstring name);
	void setGlobalName(std::wstring name);
	void setDefaultFriendlyName();
	std::wstring getFullType() {
		return (_meta.model + L"(" + _meta.type + L")");
	};
	std::wstring getFullConnectionType() {
		return (_comm.connectionType + L"(" + _comm.connectionSubType + L"-" + _comm.connectionHardware + L")");
	};
	virtual std::wstring getTypeWithDetail() { return _meta.type; };
	std::wstring getLogFilename() { return _generalStat.logFilename; };
	std::wstring getModel() { return _meta.model; };
	std::wstring getAppearance() { return _comm.appearance; };
	std::wstring getID() { return _comm.identifier; };
	std::wstring getReferenceName();
	// as in hardware setup
	std::wstring getGlobalName();
	std::wstring getFriendlyName();
	std::wstring getNameInScript() { return _meta.scriptedName; };
	std::wstring getMetaByName(std::wstring name);
	std::wstring setMetaByName(std::wstring name, std::wstring val);
	void setReferenceName(std::wstring newName) { _meta.referenceName = newName; };
	std::wstring getDisplayName() { return _meta.friendlyName; };
	// not defined for general slave 
	int virtual setOnlineSimu(int flag, float rate) { return -1; };
	int virtual setOfflineSimu(int flag, float rate) { _meta.isSimulated = 1; return 1; };
	int virtual setNoSimu() { _meta.isSimulated = 0; return 1; };
	void virtual setNewRecordFile(std::wstring filename);
	int virtual writeToRecordFile(bool newFile) { return 1; };
	// special treatment in presendingconfig
	bool virtual isCoordinator() { return false; }

	int virtual initClass() { return 1; };
	/*
	Connect
	*/
	bool isBuiltIn() {
		return _meta.isBuiltIn > 0;
	};
	virtual int isConnected() { return _comm._conStat == ConnectionStatus::Connected; };
	virtual int isCnnBroken() { return _comm._conStat == ConnectionStatus::Broken; };
	int updateConnectFromEmbodiment();
	virtual int shouldConnect() { return _meta.isExpecting; };
	bool isTemplate() { return (_meta.isDiscoveryTemplate == 1); };
	// some slave responde differently with a coordinator
	virtual void setCoordinator(GeneralSlave* coordinatorIn) { _comm.coordinator = coordinatorIn; };
	std::wstring getConnectionStatStr();
	static HandShakeProtocol makeGeneralSerialHandshake(
		std::wstring model, std::wstring id, int discoveryMode);
	static HandShakeProtocol makeGeneralSerialHandshake(std::vector<std::string> handShakePreSends, std::vector <std::string> handShakePreReplys,
		std::wstring model, std::wstring id, int discoveryMode);
	int virtual connect(int discoverMode);
	int virtual confirmConnect();
	int virtual reConnect();
	void updateAfterConnectThroughExternalDriver();
	int virtual updateOnConnectionBroken();
	// allow reconnect
	int virtual softDisConnect();
	// actually cannot reconnect.
	int virtual hardDisConnect();

	int virtual connectThroughUSB(int discoverMode);
	// discoverMode not inplemented
	int virtual connectThroughTBridge(int discoverMode);

	int virtual logConnectStart() { return -1; };
	int virtual logConnectSuccess() { return -1; };
	int virtual logConnectComplete() { return -1; };
	bool virtual shareLink(GeneralSlave* reference);

	// after reconnect, assuming they are out-of-date
	virtual void resetBufferedVolatileVariables() {};

	void addAvatar(GeneralSlave* slave);

	std::wstring summarizeConnection(int indent);

	/*
	Execute
	*/
	bool isSimu() { return _meta.isSimulated > 0; };
	int virtual simuStart(float rate) { return 1; };
	// must implement both real and simu
	// readIndex used to avoid reading multiple times in one look. readIndex=0 force a new reading
	int virtual readOut(uint64_t readIndex = 0) {
		return 1;
	};
	// a slower read posibly with some volatile stats
	int virtual readOutInIdle(uint64_t loopIndex = 0) { return readOut(loopIndex); };

	int queueCommandBase(std::string cmd, int replyLineNum);
	int queueCommandBase(std::vector<uint8_t> cmd, int replyLineNum);
	virtual int issueBufferedCommand();
	virtual std::wstring summarizeSchedule(int indent) { return L""; };
	bool isActivated() { return _generalStat._exeStat == ExecutionStatus::Activated; };
	bool isStandBy() { return _generalStat._exeStat == ExecutionStatus::StandBy; };
	// when enrolled, may be activated at start
	void enrollInProtocol();
	bool isEnrolledInProtocol();
	// when discharged, may be deactivated at start
	void dischargeFromProtocol();
	// just activate. activated device may not doing any job but keep updating
	virtual void activate();
	// just deactivate. deactived device skip even update without schedule
	virtual void deactivate();
	// actually start the schedule, not just updating
	virtual int startSchedule() {
		return 1;
	};
	virtual int abortSchedule() {
		_generalStat.scheduleSetTime = 0;
		return 1;
	}
	virtual int watchUntilComplete() { return 1; };

	virtual void protocolStartReset() {};

	//bool getActivatedInScript() { return _meta.deactivedInScript == 0; };
	bool isLoggingDetails();

	template<class SlaveClass>
	static SlaveClass* getSlaveByName(std::vector< SlaveClass*>& slaves, std::wstring refName);

	struct Meta {
		int slaveIdx = 0;
		bool isExpecting = false;
		// will be copied to a new one if connected
		bool isDiscoveryTemplate = false;
		// will be removed in a normal connection
		bool isDiscoveredTemp = false;
		bool isRecordFileWriten = false;
		// can have >0 value for slave-specific meaning
		int isSimulated = 0;
		// not following general config
		int isSimuSpecified = 0;
		int isNormal = 1;
		// do not disconnect upon loading script
		int isBuiltIn = 0;

		std::wstring dynamicLocation;

		std::wstring platform;
		// thermal, pump, liquid (except pump), etc.
		// special case: robotic, may further indicate as thermal, liquid, etc...
		// filled automatically
		std::wstring type;
		// some may belong to multiple type, by config
		std::wstring alsoType;
		//std::wstring subType;
		// from descriptor
		std::wstring model;

		std::wstring friendlyName;
		std::wstring referenceName;
		// by reflection
		std::wstring scriptedName;
		// as in setup
		std::wstring globalName;

		// to be parsed by individual classes
		std::wstring controlFunctions;
		// used upon connection if not given
		std::wstring defaultFunctions;
		// to be parsed by individual classes
		std::wstring controlParameters;
		// used upon connection if not given
		std::wstring defaultParameters;
		ParaMapSlave controlModes;

		std::wstring partOfComboSlave;

	}_meta;

	struct SimulationMeta {
		clock_t simuStartTime = 0;
		clock_t simuUpdateTime = 0;
	}_simuMeta;

	struct GeneralStat {
		uint64_t readingIndex = 0;

		ExecutionStatus _jobInProtocol = ExecutionStatus::StandBy;
		// may be deactivated to save time slots
		//int deactivedInScript = 0;
		ExecutionStatus _exeStat = ExecutionStatus::StandBy;

		clock_t scheduleSetTime = 0;

		// consistent in one session, used by pointer reference so must no be replaced
		std::wstring logFilename;
		// changes between each excution
		std::wstring recordFilename;
	}_generalStat;

	// set directly by master
	struct Callbacks {
		bool* isLoggingDetailRef = nullptr;
		bool flagForwardToMaster = true;
		bool flagLogLocal = false;
		std::wstring nameInCB, idInCB;

		// send result to master
		MasterLogCallback sentCB2Master = NULL;
		MasterLogStateCallback receiveCB2Master = NULL;
		MasterLogStateCallback timeoutCB2Master = NULL;
		std::string allCBBuffer;

		/*
		Wrapper explaination
		there are multi instances of generalPrototype
		for the caller (slave comm), the implementation is unknown so it has to call a static method
		that call must be directed to a instance-bond one to be sent to higher level with its name attached
		*/
		// called by slave
		void serialSentCB(bool success, const void* log, int logLen, const void* data, int dataLen);
		// called by slave
		void serialReceiveCB(bool success, const void* log, int logLen, const void* data, int dataLen);
		static void serialSentCBWrap(int index, bool success, const void* log, int logLen, const void* data, int dataLen);
		static void serialReceiveCBWrap(int index, bool success, const void* log, int logLen, const void* data, int dataLen);
	}_callbacks;

	struct Communication {
		std::wstring connectionType;
		// "simulated"
		std::wstring connectionSubType;
		std::wstring connectionHardware;
		std::wstring appearance;
		std::wstring identifier;

		// may get zones and other config from other kernel
		std::wstring associatedKernels;

		// set dynamically, allow to cause reboot so do not send in reconnect
		std::vector<std::string> handShakePreSends;
		std::vector<std::string> handShakePreReceives;

		// some slaves are connected through a coordinator
		// need to call ask coordinator to activate control of the slave
		GeneralSlave* coordinator = nullptr;
		// of this slave, on the coordinator
		int coordinatorIndex = -100;
		// temperary, used until the coordinator is linked
		//std::wstring _coordinatorName;

		CommChannel* commHandle = nullptr;
		int isCommChOwner = 1;
		ConnectionStatus _conStat = ConnectionStatus::Missing;

		// an avatar is a slave sharing the exact connection with another slave. an embodiment is the one shared
		GeneralSlave* embodiment = nullptr;
		// added by the avatars at init
		std::vector<GeneralSlave*> avatars;

		U8Vec commDataBuffer;
		int defaultTimeout = 750;
		// ESP-NOW sometimes report false cannot-send error
		int SOFT_TIMEOUT_TBRIDGE = 400;
		std::string newReceivedDt;
		std::string lastReceivedDt;

		void markBroken();
		void markRecovery();

		~Communication();
	}_comm;

	// random access commands
	struct RandomCommandBuffer {
		struct Command {
			U8Vec cmd;
			U8Vec reply;
			int replyLineNum = 0;
			Command() {};
			Command(const U8Vec& newCmd) { cmd = newCmd; };
		};
		std::vector<Command> cmdList;
		bool isConsuming = false;
		bool isAdding = false;
		int add(U8Vec newCmd, int replyLnNum);
		Command comsume();
	}_rdCmds;

	bool setSerialLogLocal(bool flag);
	bool setSerialForward2Master(bool flag);
	void logLineGlobal(std::string line);
	// config the comm chain to point this
	int preSendingConfig();
	// in connect(), no need to config self as not connected yet
	// both calledIndex and configSelf are used by the overwriting method of a coordinator, see TAutomatonFrameCoordinator::preSendingConfig
	// calledIndex/configSelf/callerIsAE used by coordinator
	virtual int preSendingConfig(std::wstring baseCallerName, int calledIndex = -1, int configSelf = -1, int callerIsAE = 0);
	virtual int sendString(std::string& cmd);
	virtual int sendU8(U8Vec& cmd);
	virtual int receive(std::string* reply, int overwriteTimeout = -1);
	virtual int checkReply(std::string reply);
	virtual int purgeReply();

	virtual int forceRebuildBridges();

	virtual std::string getTempFlag(std::string key, std::string defaultVal);
	virtual std::string setTempFlag(std::string key, std::string value);
	virtual void resetTempFlags();
	// the index in readOut(
	virtual void resetReadingIndex() { _generalStat.readingIndex = 0; };

protected:
	/*
	Connect
	*/
	int virtual simuConnectUpdate();
	int virtual simuDisConnect();
	static void makeGeneralSerialHandshakeCore(HandShakeProtocol& protocol,
		std::vector<std::string> handShakePreSends, std::vector <std::string> handShakePreReplys, std::wstring model, std::wstring id, int discoveryMode);
	int virtual connectThroughUSBExe(HandShakeProtocol& protocol, std::vector<std::wstring> hwids, int discoverMode);
	int virtual connectThroughUSBSimu(HandShakeProtocol& protocol, std::vector<std::wstring> hwids, int discoverMode);
	int connectThroughTBridgeNonRoot(HandShakeProtocol& protocol, int discoverMode);
	// not marked as connected yet
	void virtual updateBeforeConnectToUsbSerial(void* serialPortManager);
	// not marked as connected yet
	void virtual updateBeforeConnectThroughBridge(GeneralSlave* bridgeRoot);
	// mark as connected at the end of this
	void virtual updateAfterConnectAny(HandShakeProtocol* protocol);

	const int LogPaddingStageTitle = 0;
	// unless specified otherwise
	const int LogPaddingStageContent = 2;
	const int LogPaddingStageDetail = 4;

	void writeLogLinePlain(std::wstring line, bool withTime = true, int padding = 0);
	void writeLogLinePlain(std::string line, bool withTime = true, int padding = 0);
	/*
	Logging of processing
	*/
	struct Logger {
		// stored somewhere for easy config
		std::wstring* logFilenameRef = nullptr;
		void writeLogLineCore(std::wstring line, bool withTime = true, int padding = 0);
	}_logger;


	struct LogBlockCtrl {
		std::wstring* groupIndentStrRef;
		std::wstring blockName;
		Logger* loggerRef;
		bool flagReleased = false;

		LogBlockCtrl() {};
		// move contructor, to correctly deal with returning from functions
		LogBlockCtrl(LogBlockCtrl&& ref);
		LogBlockCtrl(std::wstring* groupIndentStrRefIn, const std::wstring& blockNameIn, Logger* loggerRefIn);
		~LogBlockCtrl();
	private:
		void addIndent();
		void dropIndent();
	};

	// overwrite this to pass the pointer of slave-specific groupIndentStr 
	virtual LogBlockCtrl& makeLogBlock(const std::string& blockName, bool withTime) {
		LogBlockCtrl toy;
		return toy;
	};

	struct CommCallback {
		int callbackIndex = -1;

		static int addMeToArray(GeneralSlave* myPtr);
		static void removeMeFromArray(int index);
		static std::vector<GeneralSlave*> allSlaveReps;
	} _commCB;

	struct Temp {
		// not guranteed.
		std::map<std::string, std::string> procFlagMap;
	}_temp;
};
typedef GeneralSlave* SlaveRef;

struct SlaveInitializer :GeneralSlave {
	SlaveInitializer();
};
extern SlaveInitializer globalSlaveInitializer;

template<class SlaveClass>
SlaveClass* GeneralSlave::getSlaveByName(std::vector< SlaveClass*>& slaves, std::wstring refName) {
	for (auto slave : slaves) {
		if (slave->getReferenceName().compare(refName) == 0) {
			return slave;
		}
	}
	return nullptr;
}

#endif